Übungsheft Modernes ABAP

Für die Deutsche Bahn / DB-Systel

Jörg Brandeis

(C) Brandeis Consulting.

Übungsszenarien in diesem Heft

Die Übungen beziehen sich größtenteils auf kleine, separate Abschnitte der Schulung.
Am Ende gibt es dann noch eine größere Aufgaben, die wir noch angehen, falls ausreichend Zeit bleibt.

Aufbau der Übungen

Am besten legt Ihr für die Übungen eine separate Klasse an. Das passiert gleich in der ersten Übung. Für jede folgende Übung wird eine einzelne Methode angelegt. Diese wird dann aus der Methode IF_OO_ADT_CLASRUN~MAIN heraus aufgerufen

Daten in den Übungen

Falls Daten benötigt werden, dann kommen diese aus den beiden Datenbanktabelle ZBC_USERS oder ZBC_TASKS. Die Struktur der Beiden sieht so aus:

Struktur von ZBC_USERS

define table zbc_users {
  key client    : abap.clnt not null;
  key user_id   : zbc_user_id not null;
  firstname     : zbc_firstname;
  lastname      : zbc_lastname;
  email         : zbc_email;
  gender        : zbc_gender;
  date_of_birth : zbc_date_of_birth;

}

Struktur von ZBC_TASKS

define table zbc_tasks {
  key client  : abap.clnt not null;
  key task_id : zbc_task_id not null;
  task_key    : zbc_task_key;
  summary     : zbc_task_summary;
  status      : zbc_task_status;
  project     : zbc_project_id;
  description : abap.char(1000);
  assignee    : zbc_user_id;
  type        : zbc_task_type;
  author      : zbc_user_id;
  changed_at  : abp_locinst_lastchange_tstmpl;
  created_at  : abp_creation_tstmpl;
  due_date    : zbc_due_date;
  solution    : zbc_solution;
  priority    : zbc_priority;
  product     : zbc_product_id;

}

Da die beiden Tabellen mit 100 bzw. 1000 Datensätzen etwas unhandlich für die Konsolenausgabe sind, könnt Ihr in den Übungen das Datenvolumen mit UP TO 10 ROWS beschränken:

    SELECT *
      FROM zbc_users
      INTO TABLE @DATA(users)
      UP TO 10 ROWS.
(C) Brandeis Consulting.

In Eclipse ausführbare Klassen

Lege eine Klasse an, die direkt in Eclipse mit F9 ausführbar ist. Bei der Ausführung soll ein Text (z.B. "Hallo Welt" ) ausgegeben werden.

Lösungshinweise

  • verwende das Interface IF_OO_ADT_CLASRUN
  • Nutze in der MAIN Methode die Methode OUT->WRITE( )

In den folgenden Übungen dieser Übungssammlung soll das Interface IF_OO_ADT_CLASRUN auch verwendet werden, um die Implementierung zu testen.

Lösungsvorschlag

CLASS zbc_string_functions DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

ENDCLASS.
CLASS zbc_string_functions IMPLEMENTATION.

  METHOD if_oo_adt_classrun~main.
    out->WRITE( 'Hallo Welt' ).
  ENDMETHOD.

ENDCLASS.
(C) Brandeis Consulting.

Zeichenkettenverarbeitung mit Funktionen

Aufgabenstellung

Erzeuge aus der Tabelle ZBC_USERS eine Benutzerliste mit den folgenden Daten:

  • USER_ID in einheitlicher Länge,
  • NAME bestehend aus
    • 1. Buchstabe des Vornamens
    • Ein Punkt, ein Leerzeichen
    • Nachname
  • Das Geburtsdatum schön formatiert in runden Klammern

Beispiel

LMILLETT      - L. Millett (08.01.1971)
SRIDGES       - S. Ridges (06.09.1961)
AROOSE        - A. Roose (03.04.1963)
AJARMAINE     - A. Jarmaine (05.11.1993)
LEDGLER       - L. Edgler (30.01.1981)
WGYRGORWICX   - W. Gyrgorwicx (19.01.1982)
LPEPPER       - L. Pepper (08.11.1981)
AHACHETTE     - A. Hachette (02.10.1991)

Tabelle ZBC_USERS

define table zbc_users {
  key client    : abap.clnt not null;
  key user_id   : zbc_user_id not null;
  firstname     : zbc_firstname;
  lastname      : zbc_lastname;
  email         : zbc_email;
  gender        : zbc_gender;
  date_of_birth : zbc_date_of_birth;
 }
(C) Brandeis Consulting.

Zeichenkettenverarbeitung - Musterlösung

Musterlösung (Lösungsvorschlag)

  METHOD if_oo_adt_classrun~main.
    SELECT *
      FROM zbc_users
      INTO TABLE @DATA(lt_users).

    LOOP AT lt_users INTO DATA(ls_users).
      out->write( |{ ls_users-user_id WIDTH = 13
                   } - { substring( val = ls_users-firstname len = 1 )
                   }. { ls_users-lastname
                   } ({ ls_users-date_of_birth DATE = USER })|
      ).
    ENDLOOP.

  ENDMETHOD.

Clean Code Hinweise

String-Templates werden teilweise sehr lang. Innerhalb der eingebetteten Ausdrücke können aber ohne Probleme auch Zeilenumbrüche stattfinden. Damit können wir die Lesbarkeit erhöhen.

(C) Brandeis Consulting.

Übung zu String Templates

Aufgabenstellung

Erstellen Sie eine Methode, die für eine Person aus der Tabelle ZBC_USERS eine Begrüßung mit Hinweis auf den nächsten Geburtstag erzeugt.
Testen Sie die Methode mit unterschiedlichen USER_ID s.

Methodensignatur

METHODS get_salutation IMPORTING user_id TYPE zbc_user_id
                       RETURNING VALUE(result) TYPE STRING.

Beispielausgabe

Hallo Herr Peter Müller, Sie feiern in 43 Tagen Ihren 22. Geburtstag!

Infos

Tabelle ZBC_USERS

define table zbc_users {
  key client    : abap.clnt not null;
  key user_id   : zbc_user_id not null;
  firstname     : zbc_firstname;
  lastname      : zbc_lastname;
  email         : zbc_email;
  gender        : zbc_gender;
  date_of_birth : zbc_date_of_birth;
 }

`

(C) Brandeis Consulting.

Musterlösung

CLASS zbc_exercises DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.
    METHODS get_salutation 
               IMPORTING user_id       TYPE zbc_user_id
               RETURNING VALUE(result) TYPE string.
ENDCLASS.

CLASS zbc_exercises IMPLEMENTATION.
  METHOD get_salutation.
    DATA salutation TYPE string.
    DATA next_birthday TYPE dats.
    DATA year TYPE char4.
    SELECT SINGLE firstname,
                  lastname,
                  gender,
                  date_of_birth
     FROM zbc_users
     WHERE user_id = @user_id
     INTO @DATA(user).

    IF user-gender = 'M' .
      salutation = 'Herr'.
    ELSE.
      salutation = 'Frau'.
    ENDIF.

    IF user-date_of_birth+4 < sy-datum+4.
      year = sy-datum(4) + 1.
    ELSE.
      year = sy-datum(4).
    ENDIF.

    next_birthday = year && user-date_of_birth+4.

    result = |Hallo { salutation
             } { user-firstname
             }, Sie feiern in { next_birthday - sy-datum
             } Tagen Ihren { 
              next_birthday(4) - user-date_of_birth(4) 
             }. Geburtstag!|.

  ENDMETHOD.

  METHOD if_oo_adt_classrun~main.
    out->write( get_salutation( user_id = '10' ) ).
  ENDMETHOD.

ENDCLASS.
(C) Brandeis Consulting.

Inline Deklaration

Lesen Sie die Spalten

  • FIRSTNAME
  • LASTNAME und
  • EMAIL

alle Benutzer aus der Datenbanktabelle ZBC_USERS in eine Interne Tabelle. Geben Sie diese in der MAIN Methode in die Konsole aus.

Verwenden Sie bei der SELECT ... INTO Klausel die Inline Deklaration mit DATA().

(C) Brandeis Consulting.

Musterlösung Inline Deklaration

  METHOD if_oo_adt_classrun~main.
    SELECT  firstname,
            lastname,
            email
     FROM zbc_users
     INTO TABLE @DATA(users).

    out->write( users ).
  ENDMETHOD.
(C) Brandeis Consulting.

VALUE Operator

Legen Sie eine Methode an und erstellen Sie darin eine RANGES Tabelle für den Datentyp DATS.
Füllen Sie diese Tabellen so, das sie eine Selektion für die Schulferien für das Schuljahr 2022/2023 von Baden-Württemberg enthält:

Ferien Zeitraum
Herbstferien 2022 Mo, 31.10. + Mi, 02.11. - Fr, 04.11.
Weihnachtsferien 2022 Mi, 21.12.2022 - Sa, 07.01.2023
Winterferien 2023 -
Osterferien 2023 Do, 06.04. + Di, 11.04. - Sa, 15.04.
Pfingstferien 2023 Di, 30.05. - Fr, 09.06.
Sommerferien 2023 Do, 27.07. - Sa, 09.09.

Selektieren Sie mit dieser Tabelle auf die Aufgabentabelle ZBC_TASKS. Wie viele Aufgaben gibt es, die in den Ferien fällig sind?

(C) Brandeis Consulting.

VALUE Operator - Musterlösung

Musterlösung

  METHOD exercise_value_operator.
    data lt_range type range of dats.

    lt_range = value #( sign = 'I' ( low = '20221031' option = 'EQ' )
                                   ( low = '20221102' option = 'BT' high = '20221104' )
                                   ( low = '20221221' option = 'BT' high = '20230107' )
                                   ( low = '20230406' option = 'EQ' )
                                   ( low = '20230530' option = 'BT' high = '20230609' )
                                   ( low = '20230727' option = 'BT' high = '20230909' ) ).

    SELECT  count( * )
      from zbc_tasks
     where due_date in @lt_range
     into @data(result).

   out->write( name = 'Anzahl von Aufgaben in den Ferien: ' data = result ).

  ENDMETHOD.
(C) Brandeis Consulting.

CORRESPONDING Operator

In der Tabelle ZBC_USERS sind Daten mit englischen Spaltennamen. Diese Daten sollen in eine interne Tabelle mit deutschen Spaltennamen übernommen werden.

Konvertieren Sie mit Hilfe des CORRESPONDING Operators die interne Tabelle USERS in die Tabelle BENUTZER mit der gewünschten Zielstruktur.

Vorlage für die Methode

Definition

    METHODS exercise_corresponding
      IMPORTING out TYPE REF TO if_oo_adt_classrun_out.

Implementierung

  METHOD exercise_corresponding.
    TYPES: BEGIN OF ts_benutzer,
             vorname    TYPE zbc_firstname,
             nachname   TYPE zbc_lastname,
             geb_dat    TYPE zbc_date_of_birth,
             geschlecht TYPE zbc_gender,
           END OF ts_benutzer.
    TYPES: tt_benutzer TYPE STANDARD TABLE OF ts_benutzer.
    DATA benutzer TYPE tt_benutzer. 

    SELECT * FROM zbc_users INTO TABLE @DATA(users).

" Hier bitte den Code ergänzen 
    
    out->write( benutzer ).
  ENDMETHOD.
(C) Brandeis Consulting.

CORRESPONDING Operator - Musterlösung

Musterlösung

  METHOD exercise_corresponding.
    TYPES: BEGIN OF ts_benutzer,
             vorname    TYPE zbc_firstname,
             nachname   TYPE zbc_lastname,
             geb_dat    TYPE zbc_date_of_birth,
             geschlecht TYPE zbc_gender,
           END OF ts_benutzer.
    TYPES: tt_benutzer TYPE STANDARD TABLE OF ts_benutzer.

    SELECT * FROM zbc_users INTO TABLE @DATA(users).

    DATA(benutzer) = CORRESPONDING tt_benutzer(  users 
                                                 MAPPING geb_dat    = date_of_birth
                                                         vorname    = firstname
                                                         nachname   = lastname
                                                         geschlecht = gender ).
    out->write( benutzer ).
  ENDMETHOD.
(C) Brandeis Consulting.

FILTER Operator

Die Daten der ZBC_USERS Tabelle sollen in zwei Listen ausgegeben werden:

  1. alle männlichen und
  2. alle weiblichen
    Benutzer. Wie können wir das mit dem FILTER Ausdruck einfach implementieren?

Lege eine neue Klasse an, die das Interface IF_OO_ADT_CLASSRUN implementiert. In der MAIN Methode kannst Du diese Vorlage verwenden:

    DATA users TYPE SORTED TABLE OF zbc_users WITH NON-UNIQUE KEY gender.
    SELECT * FROM zbc_users INTO TABLE @users UP TO 10 ROWS.

    out->write( name = 'Damen: '
                data = "Hier bitte auf die Damen filtern 
                 ).

    out->write( name = 'Herren: '
                data = "Hier bitte auf die Herren filtern
                ).
(C) Brandeis Consulting.

FILTER Operator - Musterlösung

Musterlösung

  METHOD exercise_filter.
    DATA users TYPE SORTED TABLE OF zbc_users WITH NON-UNIQUE KEY gender.
    SELECT * FROM zbc_users INTO TABLE @users UP TO 10 ROWS.

    out->write( name = 'Damen: '
                data = FILTER #( users WHERE gender = 'F' ) ).

    out->write( name = 'Herren: '
                data = FILTER #( users WHERE gender = 'M' ) ).

  ENDMETHOD.
(C) Brandeis Consulting.

REDUCE Operator

Aufgabenstellung

Erstellen Sie eine Methode, die aus einer internen Tabelle mit Benutzern (Struktur der DB-Tabelle ZBC_USERS) eine Zeichenkette macht, die eine Aufzählung der mit Komma getrennten Vornamen enthält.

Eingabetabelle

CLIENT USER_ID FIRSTNAME LASTNAME ...
100 0000000005 Heidi Wardington ...
100 0000000006 Constantina Atton ...
100 0000000009 Martica Elkins ...
100 0000000001 Emmye Alywen ...
100 0000000002 Yolande Gullick ...
... ... ... ... ...

Ergebnis

Heidi, Constantina, Martica, Emmye, Yolande, ...

Lösungshinweise / Hilfestellung

Definition des REDUCE Operators

... REDUCE type(
      [let_exp]
      INIT {x1 = rhs1}|{<x1> = wrexpr1}|{x1|<x1> TYPE dtype1}
           {x2 = rhs2}|{<x2> = wrexpr2}|{x2|<x2> TYPE dtype2}
           ...
      FOR for_exp1
      FOR for_exp2
      ...
      NEXT ...
           {x1 =|+=|-=|*=|/=|&&= rhs1}|{<x1> =|+=|-=|*=|/=|&&= wrexpr1}
           {x2 =|+=|-=|*=|/=|&&= rhs2}|{<x2> =|+=|-=|*=|/=|&&= wrexpr2}
           ... ) ...

Beispiel mit dem Reduce Operator

    SELECT  summary
      FROM zbc_tasks
      INTO TABLE @DATA(lt_tasks)
      UP TO 10 ROWS.

    DATA(lv_result) = REDUCE string( INIT r  TYPE string
                                     FOR LINE IN lt_tasks
                                     NEXT
                                      r &&= LINE-summary(1)
                                      ).
    out->WRITE( lv_result ).
(C) Brandeis Consulting.

REDUCE Operator - Musterlösung

Musterlösung

  METHOD exercise_reduce.
    SELECT *
      FROM zbc_users
      INTO TABLE @DATA(users)
      UP TO 10 ROWS.

    out->write( reduce string( INIT result type string
                                    separator type char2
                               FOR ls_line in users
                               NEXT
                               result   &&= |{ separator }{ ls_line-firstname }|
                               separator  = ', ' ) ) .

  ENDMETHOD.

Der SEPARATOR wird erst nach dem ersten Durchlaufs festgelegt, damit die Zeichenkette nicht mit einem Komma beginnt. Allerdings ist es nicht so einfach, das sauber hinzubekommen, da das Leerzeichen am Ende der Zeichenkette in dem Beispiel entfernt wird. Habt Ihr eine Idee, wie man das lösen kann?

Mögliche Optionen:

  • Verwendung von Strings statt normalen Zeichenketten
  • Der SWITCH Operator
  • ...
(C) Brandeis Consulting.

Übungsaufgabe Unit Test

Erstellen Sie eine Methode, die aus einer internen Tabelle mit Benutzern (Struktur der DB-Tabelle ZBC_USERS) eine Zeichenkette macht, die eine Aufzählung der mit Komma getrennten Vornamen enthält.

Eingabetabelle

CLIENT USER_ID FIRSTNAME LASTNAME ...
100 0000000005 Heidi Wardington ...
100 0000000006 Constantina Atton ...
100 0000000009 Martica Elkins ...
100 0000000001 Emmye Alywen ...
100 0000000002 Yolande Gullick ...
... ... ... ... ...

Ergebnis

Heidi, Constantina, Martica, Emmye, Yolande, ...

CLASS zjb_ut DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    types tt_users type STANDARD TABLE OF zbc_users WITH DEFAULT KEY. 
    methods create_list importing it_users type tt_users
                        returning value(result) type string. 
ENDCLASS.



CLASS zjb_ut IMPLEMENTATION.

  METHOD create_list.

  ENDMETHOD.

ENDCLASS.
(C) Brandeis Consulting.

Übungsaufgabe Game Of Life

Aufgabenstellung

Schaut Euch den Wikipedia-Artikel zum Thema Conways Spiel des Lebens an. Wir wollen dieses Spiel als ABAP Programm implementieren. Dabei sollen alle Dinge beachtet und genutzt werden, die wir in den letzten Tagen gelernt haben:

  • Konsequent sauberer Code
  • Keine Änderung am Code ohne fehlerhaften UnitTest
  • Nutzung des modernen, Ausdrucksorientierten ABAP Stils

Das quadratische Spielbrett soll mit einer beliebigen Größe initialisiert werden können. Mit einer Methode SET(X,Y) sollen einzelne Zellen zum Leben erweckt werden. Die Methode WRITE() soll das Spielbrett als Text mit X und - ausgeben:

--X---
---X--
-XXX--
------
------
------
------
-X-X--
--XX--
--X---
------
------
------
---X--
-X-X--
--XX--
------
------
------
--X---
---XX-
--XX--
------
------
------
---X--
----X-
--XXX-
------
------
------
------
--X-X-
---XX-
---X--
------

Und dann soll das Spielbrett mit NEXT_STEP() einen Schritt weiter gehen, d.h. die ganzen Regeln werden auf die bestehende Brett angewendet.

Bitte bildet bei dieser Übung Teams.
(C) Brandeis Consulting.

Game Of Life - Lösungsvorschlag

Definition der Klasse ZBC_BOARD

CLASS zbc_board DEFINITION PUBLIC FINAL CREATE PUBLIC .

  PUBLIC SECTION.
    TYPES: BEGIN OF ENUM estatus,
             alive ,
             dead ,
           END OF ENUM estatus.

    METHODS write       IMPORTING out  TYPE REF TO if_oo_adt_classrun_out.
    METHODS constructor IMPORTING size TYPE int4.
    METHODS set         IMPORTING x    TYPE int4
                                  y    TYPE int4.
    METHODS set_blinker IMPORTING x    TYPE int4
                                  y    TYPE int4.
    METHODS next_step.

  PRIVATE SECTION.
    TYPES:    BEGIN OF ts_field,
                x           TYPE int4,
                y           TYPE int4,
                status      TYPE estatus,
                next_status TYPE estatus,
              END OF ts_field.
    TYPES     tt_fields  TYPE SORTED TABLE OF ts_field WITH UNIQUE KEY x y.
    DATA      mt_fields  TYPE tt_fields.
    CONSTANTS char_dead  TYPE c VALUE '-'.
    CONSTANTS char_alive TYPE c VALUE 'X'.

    METHODS get                       IMPORTING x             TYPE int4
                                                y             TYPE int4
                                      RETURNING VALUE(result) TYPE estatus.
    METHODS count_neigbours           IMPORTING x             TYPE int4
                                                y             TYPE int4
                                      RETURNING VALUE(result) TYPE int4.
    METHODS calculate_next_status     IMPORTING x             TYPE int4
                                                y             TYPE int4
                                      RETURNING VALUE(result) TYPE estatus.
    METHODS switch_to_next_status.    
ENDCLASS.

Implementierung (1/2)

CLASS zbc_board IMPLEMENTATION.

  METHOD constructor.
    DO size TIMES.
      DATA(x) = sy-index.
      DO size TIMES.
        DATA(y) = sy-index.
        INSERT VALUE #( x = x
                        y = y
                        status = dead ) INTO TABLE mt_fields.
      ENDDO.
    ENDDO.
  ENDMETHOD.

  METHOD write.
    DATA line TYPE string.
    out->write( |\n| ).
    LOOP AT mt_fields INTO DATA(ls_field)
                      GROUP BY ls_field-y ASCENDING
                      INTO DATA(lg_group).
      CLEAR line.
      LOOP AT GROUP lg_group INTO DATA(ls_group).

        line &&= SWITCH char1( ls_group-status WHEN alive THEN char_alive
                                               WHEN dead  THEN char_dead ).
      ENDLOOP.

      out->write( line ).
    ENDLOOP.
  ENDMETHOD.

(C) Brandeis Consulting.

Musterlösung, 2. Seite


  METHOD set.
    mt_fields[ x = x
               y = y ]-status = alive.
  ENDMETHOD.

  METHOD get.
    TRY.
        result = mt_fields[ x = x
                            y = y ]-status.
      CATCH cx_root.
        result = dead.
    ENDTRY.
  ENDMETHOD.

  METHOD set_blinker.
    set( x = x     y = y  ).
    set( x = x + 1 y = y  ).
    set( x = x + 2 y = y  ).
  ENDMETHOD.

  METHOD next_step.
    LOOP AT mt_fields ASSIGNING FIELD-SYMBOL(<ls_field>).
      <ls_field>-next_status = calculate_next_status( x = <ls_field>-x
                                                      y = <ls_field>-y ).
    ENDLOOP.
    switch_to_next_status( ).
  ENDMETHOD.

  METHOD calculate_next_status.

    DATA(counter) = count_neigbours( x = x
                                     y = y ).
    DATA(status)  = get( x = x
                         y = y ).

    result = COND estatus(  WHEN counter = 3     THEN alive
                            WHEN counter = 2
                             AND status  = alive  THEN alive
                            ELSE dead ).
  ENDMETHOD.

  METHOD count_neigbours.
    result       = SWITCH int4( get( x = x + 1 y = y     ) WHEN dead THEN 0
                                                            WHEN alive THEN 1 ).
    result      += SWITCH int4( get( x = x + 1 y = y - 1 ) WHEN dead THEN 0
                                                            WHEN alive THEN 1 ).
    result      += SWITCH int4( get( x = x + 1 y = y + 1 ) WHEN dead THEN 0
                                                            WHEN alive THEN 1 ).
    result      += SWITCH int4( get( x = x - 1 y = y     ) WHEN dead THEN 0
                                                            WHEN alive THEN 1 ).
    result      += SWITCH int4( get( x = x - 1 y = y - 1 ) WHEN dead THEN 0
                                                            WHEN alive THEN 1 ).
    result      += SWITCH int4( get( x = x - 1 y = y + 1 ) WHEN dead THEN 0
                                                            WHEN alive THEN 1 ).
    result      += SWITCH int4( get( x = x     y = y - 1 ) WHEN dead THEN 0
                                                            WHEN alive THEN 1 ).
    result      += SWITCH int4( get( x = x     y = y + 1 ) WHEN dead THEN 0
                                                            WHEN alive THEN 1 ).
  ENDMETHOD.

  METHOD switch_to_next_status.
    LOOP AT mt_fields ASSIGNING FIELD-SYMBOL(<line>).
      <line>-status      = <line>-next_status.
      <line>-next_status = dead.
    ENDLOOP.
  ENDMETHOD.

ENDCLASS.
(C) Brandeis Consulting.